from struct import pack, unpack
import socket
import os, shutil
import math
import sys
import re
from ntlm import NTLM, GSS
import nbnq
try:
	import sqlite3
except:
	from pysqlite2 import dbapi2 as sqlite3
	
#this is a test change!!sdfasdffsdasdfasdf

# SMB2 Commands  
SMB2_CMD_NEGOTIATE = 0x0000
SMB2_CMD_SESSION_SETUP = 0x0001
SMB2_CMD_LOGOFF = 0x0002
SMB2_CMD_TREE_CONNECT = 0x0003
SMB2_CMD_TREE_DISCONNECT = 0x0004
SMB2_CMD_CREATE = 0x0005
SMB2_CMD_CLOSE = 0x0006
SMB2_CMD_FLUSH = 0x0007
SMB2_CMD_READ = 0x0008
SMB2_CMD_WRITE = 0x0009
SMB2_CMD_LOCK = 0x000A
SMB2_CMD_IOCTL = 0x000B
SMB2_CMD_CANCEL = 0x000C
SMB2_CMD_ECHO = 0x000D
SMB2_CMD_QUERY_DIRECTORY = 0x000E
SMB2_CMD_CHANGE_NOTIFY = 0x000F
SMB2_CMD_QUERY_INFO = 0x0010
SMB2_CMD_SET_INFO = 0x0011
SMB2_CMD_OPLOCK_BREAK = 0x0012

# SMB2 Flags
SMB2_FLAGS_SERVER_TO_REDIR = 0x00000001 # When set, indicates the message is a response rather than a request. This MUST be set on responses sent from the server to the client, and MUST NOT be set on requests sent from the client to the server.
SMB2_FLAGS_ASYNC_COMMAND = 0x00000002 # When set, indicates that this is an ASYNC SMB2 header. Always set for headers of the form described in this section.
SMB2_FLAGS_RELATED_OPERATIONS = 0x00000004 # When set in an SMB2 request, indicates that this request is a related operation in a compounded request chain. The use of this flag in an SMB2 request is as specified in 3.2.4.1.4. When set in an SMB2 response, indicates that the request corresponding to this response was part of a related operation in a compounded request chain. The use of this flag in an SMB2 response is as specified in 3.3.5.2.6.2.
SMB2_FLAGS_SIGNED = 0x00000008 # When set, indicates that this packet has been signed. The use of this flag is as specified in 3.1.5.1.
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000 # When set, indicates that this command is a Distributed File System (DFS) operation. The use of this flag is as specified in 3.3.5.9.

# Requested Oplock Level
SMB2_OPLOCK_LEVEL_NONE = 0x00  # No oplock is requested.
SMB2_OPLOCK_LEVEL_II = 0x01  # A level II oplock is requested.
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08  # An exclusive oplock is requested.
SMB2_OPLOCK_LEVEL_BATCH = 0x09  # A batch oplock is requested.
SMB2_OPLOCK_LEVEL_LEASE = 0xFF  # A lease is requested. If set, the request packet MUST contain an SMB2_CREATE_REQUEST_LEASE (section 2.2.13.2.8) create context. This value is only valid for the SMB 2.1 dialect.


# PySMB2 Create Commands
SMB2_CREATE_LIST 		= 0x01
SMB2_CREATE_RENAME 		= 0x02
SMB2_CREATE_COPYREAD 	= 0x03
SMB2_CREATE_COPYWRITE 	= 0x04
SMB2_CREATE_MKDIR 		= 0x05
SMB2_CREATE_EXIST 		= 0x06
SMB2_CREATE_DELETE 		= 0x07
SMB2_CREATE_LOCK 		= 0x08

# Directory Access Mask 
FILE_LIST_DIRECTORY 	= 0x00000001  # This value indicates the right to enumerate the contents of the directory.
FILE_ADD_FILE 			= 0x00000002  # This value indicates the right to create a file under the directory.
FILE_ADD_SUBDIRECTORY 	= 0x00000004  # This value indicates the right to add a sub-directory under the directory.
FILE_READ_EA 			= 0x00000008  # This value indicates the right to read the extended attributes of the directory.
FILE_WRITE_EA 			= 0x00000010  # This value indicates the right to write or change the extended attributes of the directory.
FILE_TRAVERSE 			= 0x00000020  # This value indicates the right to traverse this directory if the server enforces traversal checking.
FILE_DELETE_CHILD 		= 0x00000040  # This value indicates the right to delete the files and directories within this directory.

# File/Pipe/Printer Access Mask
FILE_READ_DATA 			= 0x00000001  # This value indicates the right to read data from the file or named pipe.
FILE_WRITE_DATA 		= 0x00000002  # This value indicates the right to write data into the file or named pipe beyond the end of the file.
FILE_APPEND_DATA 		= 0x00000004  # This value indicates the right to append data into the file or named pipe.
FILE_READ_EA 			= 0x00000008  # This value indicates the right to read the extended attributes of the file or named pipe.
FILE_WRITE_EA 			= 0x00000010 # This value indicates the right to write or change the extended attributes to the file or named pipe.
FILE_EXECUTE 			= 0x00000020  # This value indicates the right to execute the file.
FILE_READ_ATTRIBUTES 	= 0x00000080  # This value indicates the right to read the attributes of the file.
FILE_WRITE_ATTRIBUTES 	= 0x00000100  # This value indicates the right to change the attributes of the file.

# Directory/File/Pipe/Printer Access Mask
DELETE 					= 0x00010000  # This value indicates the right to delete the file.
READ_CONTROL 			= 0x00020000  # This value indicates the right to read the security descriptor for the file or named pipe.
WRITE_DAC 				= 0x00040000  # This value indicates the right to change the discretionary access control list (DACL) in the security descriptor for the file or named pipe. For the DACL data structure, see ACL in [MS-DTYP].
WRITE_OWNER 			= 0x00080000  # This value indicates the right to change the owner in the security descriptor for the file or named pipe.
SYNCHRONIZE 			= 0x00100000  # SMB2 clients set this flag to any value.<33>
ACCESS_SYSTEM_SECURITY 	= 0x01000000  # This value indicates the right to read or change the system access control list (SACL) in the security descriptor for the file or named pipe. For the SACL data structure, see ACL in [MS-DTYP].<35>
MAXIMUM_ALLOWED 		= 0x02000000  # This value indicates that the client is requesting an open to the file with the highest level of access the client has on this file. If no access is granted for the client on this file, the server MUST fail the open with STATUS_ACCESS_DENIED.
GENERIC_ALL 			= 0x10000000  # This value indicates a request for all the access flags that are previously listed except MAXIMUM_ALLOWED and ACCESS_SYSTEM_SECURITY.
GENERIC_EXECUTE 		= 0x20000000  # This value indicates a request for the following combination of access flags listed above: FILE_READ_ATTRIBUTES| FILE_EXECUTE| SYNCHRONIZE| READ_CONTROL.
GENERIC_WRITE 			= 0x40000000  # This value indicates a request for the following combination of access flags listed above: FILE_WRITE_DATA| FILE_APPEND_DATA| FILE_WRITE_ATTRIBUTES| FILE_WRITE_EA| SYNCHRONIZE| READ_CONTROL.
GENERIC_READ 			= 0x80000000  # This value indicates a request for the following combination of access flags listed above: FILE_READ_DATA| FILE_READ_ATTRIBUTES| FILE_READ_EA| SYNCHRONIZE| READ_CONTROL.

# Share Access
FILE_SHARE_READ 		= 0x00000001  # When set, indicates that other opens are allowed to read this file while this open is present. This bit MUST NOT be set for a named pipe or a printer file. Each open creates a new instance of a named pipe. Likewise, opening a printer file always creates a new file.
FILE_SHARE_WRITE 		= 0x00000002  # When set, indicates that other opens are allowed to write this file while this open is present. This bit MUST NOT be set for a named pipe or a printer file. Each open creates a new instance of a named pipe. Likewise, opening a printer file always creates a new file.
FILE_SHARE_DELETE 		= 0x00000004  # When set, indicates that other opens are allowed to delete or rename this file while this open is present. This bit MUST NOT be set for a named pipe or a printer file. Each open creates a new instance of a named pipe. Likewise, opening a printer file always creates a new file.

# Create Disposition
FILE_SUPERSEDE 			= 0x00000000  # If the file already exists, supersede it. Otherwise, create the file. This value SHOULD NOT be used for a printer object.<23>
FILE_OPEN 				= 0x00000001  # If the file already exists, return success; otherwise, fail the operation. MUST NOT be used for a printer object.
FILE_CREATE 			= 0x00000002  # If the file already exists, fail the operation; otherwise, create the file.
FILE_OPEN_				= 0x00000003  # IF Open the file if it already exists; otherwise, create the file. This value SHOULD NOT be used for a printer object.<24>
FILE_OVERWRITE 			= 0x00000004  # Overwrite the file if it already exists; otherwise, fail the operation. MUST NOT be used for a printer object.
FILE_OVERWRITE_IF		= 0x00000005  # Overwrite the file if it already exists; otherwise, create the file. This value SHOULD NOT be used for a printer object.<25>

# SMB2 Query Info Type
SMB2_0_INFO_FILE 		= 0x01  # The file information is requested. 
SMB2_0_INFO_FILESYSTEM 	= 0x02  # The underlying object store information is requested.
SMB2_0_INFO_SECURITY 	= 0x03  # The security information is requested. 
SMB2_0_INFO_QUOTA 		= 0x04  # The underlying object store quota information is requested.

# FSCTL/IOCTL Control codes for SMB2 IOCTL Request Packet
FSCTL_DFS_GET_REFERRALS 		= 0x00060194
FSCTL_PIPE_PEEK 				= 0x0011400C
FSCTL_PIPE_WAIT 				= 0x00110018
FSCTL_PIPE_TRANSCEIVE 			= 0x0011C017
FSCTL_SRV_COPYCHUNK 			= 0x001440F2
FSCTL_SRV_ENUMERATE_SNAPSHOTS 	= 0x00144064
FSCTL_SRV_REQUEST_RESUME_KEY 	= 0x00140078
FSCTL_SRV_READ_HASH 			= 0x001441bb
FSCTL_SRV_COPYCHUNK_WRITE 		= 0x001480F2
FSCTL_LMR_REQUEST_RESILIENCY 	= 0x001401D4

# Create Options
FILE_DIRECTORY_FILE 			= 0x00000001  # The file being created or opened is a directory file. With this flag, the CreateDisposition field MUST be set to FILE_CREATE or FILE_OPEN_IF. With this flag, only the following CreateOptions values are valid: FILE_WRITE_THROUGH, and FILE_OPEN_FOR_BACKUP_INTENT. If the file being created or opened already exists and is not a directory file and FILE_CREATE is specified in the CreateDisposition field, then the server MUST fail the request with STATUS_OBJECT_NAME_COLLISION. If the file being created or opened already exists and is not a directory file and FILE_CREATE is not specified in the CreateDisposition field, then the server MUST fail the request with STATUS_NOT_A_DIRECTORY. The server MUST fail an invalid CreateDisposition field or an invalid combination of CreateOptions flags with STATUS_INVALID_PARAMETER. 
FILE_WRITE_THROUGH 				= 0x00000002  # The server MUST propagate writes to this open to persistent storage before returning success to the client on write operations.
FILE_SEQUENTIAL_ONLY 			= 0x00000004  # This indicates that the application intends to read or write at sequential offsets using this handle, so the server SHOULD optimize for sequential access. However, the server MUST accept any access pattern. This flag value is incompatible with the FILE_RANDOM_ACCESS value.
FILE_NO_INTERMEDIATE_BUFFERING 	= 0x00000008  # The server or underlying object store SHOULD NOT cache data at intermediate layers and SHOULD allow it to flow through to persistent storage.
FILE_SYNCHRONOUS_IO_ALERT 		= 0x00000010  # This bit SHOULD be set to 0 and MUST be ignored by the server.<27>
FILE_SYNCHRONOUS_IO_NONALERT 	= 0x00000020  # This bit SHOULD be set to 0 and MUST be ignored by the server.<28>
FILE_NON_DIRECTORY_FILE 		= 0x00000040  # The file being opened MUST NOT be a directory file or the server MUST fail the request with STATUS_FILE_IS_A_DIRECTORY. This flag MUST NOT be used with FILE_DIRECTORY_FILE or the server MUST fail the request with STATUS_INVALID_PARAMETER.
FILE_COMPLETE_IF_OPLOCKED 		= 0x00000100  # This bit SHOULD be set to 0 and MUST be ignored by the server.<29>
FILE_NO_EA_KNOWLEDGE 			= 0x00000200  # The caller does not understand how to handle extended attributes. If the request includes an SMB2_CREATE_EA_BUFFER create context, then the server MUST fail this request with STATUS_ACCESS_DENIED. If extended attributes with the FILE_NEED_EA flag (see [MS-FSCC] section 2.4.15) set are associated with the file being opened, then the server MUST fail this request with STATUS_ACCESS_DENIED.
FILE_RANDOM_ACCESS 				= 0x00000800  # This indicates that the application intends to read or write at random offsets using this handle, so the server SHOULD optimize for random access. However, the server MUST accept any access pattern. This flag value is incompatible with the FILE_SEQUENTIAL_ONLY value. If both FILE_RANDOM_ACCESS and FILE_SEQUENTIAL_ONLY are set, then FILE_SEQUENTIAL_ONLY is ignored.
FILE_DELETE_ON_CLOSE 			= 0x00001000  # The file MUST be automatically deleted when the last open request on this file is closed. When this option is set, the DesiredAccess field MUST include the DELETE flag. This option is often used for temporary files.
FILE_OPEN_BY_FILE_ID 			= 0x00002000  # This bit SHOULD be set to 0 and the server MUST fail the request with a STATUS_NOT_SUPPORTED error if this bit is set.<30>
FILE_OPEN_FOR_BACKUP_INTENT 	= 0x00004000  # The file is being opened for backup intent. That is, it is being opened or created for the purposes of either a backup or a restore operation. The server MAY check to ensure that the caller is capable of overriding whatever security checks have been placed on the file to allow a backup or restore operation to occur. The server MAY check for access rights to the file before checking the DesiredAccess field.
FILE_NO_COMPRESSION 			= 0x00008000  # The file cannot be compressed.
FILE_RESERVE_OPFILTER 			= 0x00100000  # This bit SHOULD be set to 0 and the server MUST fail the request with a STATUS_NOT_SUPPORTED error if this bit is set.<31>
FILE_OPEN_REPARSE_POINT 		= 0x00200000  # If the file or directory being opened is a reparse point, open the reparse point itself rather than the target that the reparse point references.
FILE_OPEN_NO_RECALL 			= 0x00400000  # In an HSM (Hierarchical Storage Management) environment, this flag means the file SHOULD NOT be recalled from tertiary storage such as tape. The recall can take several minutes. The caller can specify this flag to avoid those delays.
FILE_OPEN_FOR_FREE_SPACE_QUERY 	= 0x00800000  # Open file to query for free space. The client SH

# Create Action
FILE_SUPERSEDED					= 0x00000000  # An existing file was deleted and a new file was created in its place.
FILE_OPENED 					= 0x00000001  # An existing file was opened.
FILE_CREATED 					= 0x00000002  # A new file was created.
FILE_OVERWRITTEN 				= 0x00000003  # An existing file was overwritten.

# File Attributes
FILE_ATTRIBUTE_ARCHIVE 			= 0x00000020  # A file or directory that requires to be archived. Applications use this attribute to mark files for backup or removal.
FILE_ATTRIBUTE_COMPRESSED 		= 0x00000800  # A file or directory that is compressed. For a file, all of the data in the file is compressed. For a directory, compression is the default for newly created files and subdirectories.
FILE_ATTRIBUTE_DIRECTORY 		= 0x00000010  # This item is a directory.
FILE_ATTRIBUTE_ENCRYPTED 		= 0x00004000  # A file or directory that is encrypted. For a file, all data streams in the file are encrypted. For a directory, encryption is the default for newly created files and subdirectories.
FILE_ATTRIBUTE_HIDDEN 			= 0x00000002  # A file or directory that is hidden. Files and directories marked with this attribute do not appear in an ordinary directory listing.
FILE_ATTRIBUTE_NORMAL 			= 0x00000080  #   A file that does not have other attributes set. This flag is used to clear all other flags by specifying it with no other flags set. This flag MUST be ignored if other flags are set.<125>
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000  # A file or directory that is not indexed by the content indexing service.
FILE_ATTRIBUTE_OFFLINE 			= 0x00001000  # The data in this file is not available immediately. This attribute indicates that the file data is physically moved to offline storage. This attribute is used by Remote Storage, which is hierarchical storage management software.
FILE_ATTRIBUTE_READONLY 		= 0x00000001 # A file or directory that is read-only. For a file, applications can read the file but cannot write to it or delete it. For a directory, applications cannot delete it, but applications can create and delete files from that directory.
FILE_ATTRIBUTE_REPARSE_POINT 	= 0x00000400  # A file or directory that has an associated reparse point.
FILE_ATTRIBUTE_SPARSE_FILE 		= 0x00000200  # A file that is a sparse file.
FILE_ATTRIBUTE_SYSTEM 			= 0x00000004  # A file or directory that the operating system uses a part of or uses exclusively.
FILE_ATTRIBUTE_TEMPORARY 		= 0x00000100  # A file that is being used for temporary storage. The operating system may choose to store this file's data in memory rather than on mass storage, writing the data to mass storage only if data remains in the file when the file is closed.

# Query Directory Flags
SMB2_RESTART_SCANS 				= 0x01  # The server MUST restart the enumeration from the beginning, but the search pattern is not changed.
SMB2_RETURN_SINGLE_ENTRY 		= 0x02  # The server MUST only return the first entry of the search results.
SMB2_INDEX_SPECIFIED 			= 0x04  # The server SHOULD<56> return entries beginning at the byte number specified by FileIndex.
SMB2_REOPEN 					= 0x10  # The server MUST restart the enumeration from the beginning, and the search pattern MUST be changed to the provided value. This often involves silently closing and reopening the directory on the server side.

# File System Attributes
FILE_SUPPORTS_USN_JOURNAL 		= 0x02000000  # The file system implements a USN change journal<102>.
FILE_SUPPORTS_OPEN_BY_FILE_ID 	= 0x01000000  # The file system supports opening a file by FileID or ObjectID<103>.
FILE_SUPPORTS_EXTENDED_ATTRIBUTES = 0x00800000  # The file system persistently stores Extended Attribute information per file<104>.
FILE_SUPPORTS_HARD_LINKS 		= 0x00400000  # The file system supports hard linking files<105>.
FILE_SUPPORTS_TRANSACTIONS 		= 0x00200000  # The volume supports transactions. <106>
FILE_SEQUENTIAL_WRITE_ONCE 		= 0x00100000  # The underlying volume is write once.
FILE_READ_ONLY_VOLUME 			= 0x00080000  # If set, the volume has been mounted in read-only mode<107>
FILE_NAMED_STREAMS 				= 0x00040000  # The file system supports named streams.
FILE_SUPPORTS_ENCRYPTION 		= 0x00020000  # The file system supports the Encrypted File System (EFS). <108>
FILE_SUPPORTS_OBJECT_IDS 		= 0x00010000  # The file system supports object identifiers.
FILE_VOLUME_IS_COMPRESSED 		= 0x00008000  # The specified volume is a compressed volume. This flag is incompatible with the FILE_FILE_COMPRESSION flag.
FILE_SUPPORTS_REMOTE_STORAGE 	= 0x00000100  # The file system supports remote storage. <109>
FILE_SUPPORTS_REPARSE_POINTS 	= 0x00000080  # The file system supports reparse points.
FILE_SUPPORTS_SPARSE_FILES 		= 0x00000040  # The file system supports sparse files.
FILE_VOLUME_QUOTAS 				= 0x00000020  # The file system supports per-user quotas.
FILE_FILE_COMPRESSION 			= 0x00000010  # The file volume supports file-based compression. This flag is incompatible with the FILE_VOLUME_IS_COMPRESSED flag.
FILE_PERSISTENT_ACLS 			= 0x00000008  # The file system preserves and enforces access control lists (ACLs).
FILE_UNICODE_ON_DISK 			= 0x00000004  # The file system supports Unicode in file and directory names. This flag applies only to file and directory names; the file system neither restricts nor interprets the bytes of data within a file.
FILE_CASE_PRESERVED_NAMES 		= 0x00000002  # The file system preserves the case of file names when it places a name on disk.
FILE_CASE_SENSITIVE_SEARCH 		= 0x00000001  # The file system supports case-sensitive file names when looking up (searching for) file names in a directory.


class SMB2(object):
	SOCKET_TIMEOUT = 10
	def __init__(self, host, user='', password='', user_domain='', server_machine_name='', port=445):
		self.port = port
		self._tree_id = {}

		# Check whether an IP address or a NetBIOS name was passed as host
		is_ip_address = r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b" 
		if (re.match(is_ip_address, host)): 
			self.ip_addresses = [host]
			self.netbios_name = None
		else: 
			nb = nbnq.NetBIOS()
			self.netbios_name = host
			self.ip_addresses = nb.get_ip(self.netbios_name)
			if (server_machine_name == ''): server_machine_name = host

		try:
			# Try all IP addresses associated with the host (if there is more than one). Throw exception if none connect.
			self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			self._socket.settimeout(SMB2.SOCKET_TIMEOUT)
			connected = False
			for i in xrange(0, len(self.ip_addresses)):
				try:
					self._socket.connect((self.ip_addresses[i], self.port))
					connected = True
				except:
					pass
			if not(connected): raise Exception("Error: Cannot connect to host.")

			# Setup SMB2 session
			self._send(SMB2Negotiate.request().new())
			p = SMB2Negotiate.response().read(self._recv())
			self.max_transaction_size = p.max_transaction_size
			self.max_read_size = p.max_read_size
			self.max_write_size = p.max_write_size
			
			session_setup = SMB2SessionSetup(user, password, user_domain, server_machine_name)
			self._send(session_setup.new_request())
			session_setup.read_response(self._recv())
			self.session_id = session_setup.header.session_id
			self._send(session_setup.new_request(session_setup.challenge_message.server_challenge, session_setup.challenge_message.target_info, self.session_id))

			try:
				session_setup.read_response(self._recv())
			except ErrorPacketException, e:
				#TODO: Add custom exception
				if (e.error_code == 0xc000006d): raise Exception("Login Failure. Check username and password.")
				else: raise
		except socket.error, msg:
			sys.stderr.write("[ERROR] %s\n" % msg)
			sys.exit(1)

	def __del__(self):
		try:
			self._socket.close()
		except AttributeError:
			pass
	
	def _send(self, data):
		""" Send a Packet """
		self._socket.send(pack('>I',len(data))+data)
	
		
	def _recv(self, length=1024):
		""" Receive a Packet """

		# Ignore packets that do not start with the SMB2 identifier
		packet = self._socket.recv(length)
		while not(packet[4:8] == "\xfe\x53\x4d\x42"):
			packet = self._socket.recv(length)
		packet_length = unpack('>I',packet[0:4])[0] + 4
		
		# If packet_length > len(packet) we need to combine additional packet segments
		while not(len(packet) >= packet_length): 
			packet += self._socket.recv(length)
			
		return packet
	
	def logoff(self):
		for item in self._tree_id:
			self._tree_disconnect(self._tree_id[item])
		self._send(SMB2Logoff.request().new(self.session_id))
		return SMB2Logoff.response().read(self._recv())

	def _tree_connect(self, share):
		share = ''.join(['\\\\', self.netbios_name, '\\', share])
		self._send(SMB2TreeConnect.request().new(self.session_id, share))
		return SMB2TreeConnect.response().read(self._recv())
	
	def _tree_disconnect(self, tree_id):
		self._send(SMB2TreeDisconnect.request().new(tree_id, self.session_id))
		return SMB2TreeDisconnect.response().read(self._recv())

	def _get_tree_id(self, share):
		try:
			tree_id = self._tree_id[share]
		except:
			tree_id = self._tree_connect(share).header.tree_id
			self._tree_id[share] = tree_id
		return tree_id

	def _create(self, path, tree_id, create_action, oplock_level):
		self._send(SMB2Create.request().new(tree_id, self.session_id, path, create_action, oplock_level))
		while True:
			try:
				p = SMB2Create.response().read(self._recv())
				return p
			except ErrorPacketException, e:
				if e.error_string == 'STATUS_PENDING': pass
				else: raise e

	def _lock(self, file_id, tree_id, file_length, flags):
		self._send(SMB2Lock.request().new(file_id, tree_id, self.session_id, file_length, flags))
		while True:
			try:
				p = SMB2Lock.response().read(self._recv())
				return p
			except ErrorPacketException, e:
				if e.error_string == 'STATUS_PENDING': pass
				else: raise e

	def _set_filename(self, new_path, file_id, tree_id):
		self._send(SMB2SetInformation.request().new(10, FileRenameInformation().new(new_path), file_id, tree_id, self.session_id))
		return SMB2SetInformation.response().read(self._recv())

	def _close(self, file_id, tree_id):
		self._send(SMB2Close.request().new(file_id, tree_id, self.session_id))
		return SMB2Close.response().read(self._recv())

	def _read(self, offset, file_length, file_id, tree_id):
		self._send(SMB2Read.request().new(offset, file_length, file_id, tree_id, self.session_id, self.max_read_size))
		return SMB2Read.response().read(self._recv(self.max_read_size+100))

	def _write(self, offset, file_length, file_id, tree_id, buffer):
		self._send(SMB2Write.request().new(offset, file_length, file_id, tree_id, self.session_id, buffer))
		return SMB2Write.response().read(self._recv())

	def _query_directory(self, file_id, tree_id, pattern, file_index):
		self._send(SMB2QueryDirectory.request().new(file_id, tree_id, self.session_id, pattern, file_index))
		return SMB2QueryDirectory.response().read(self._recv(self.max_transaction_size))

	def _query_info(self, file_id, tree_id, info_type, file_info_class):
		self._send(SMB2QueryInfo.request().new(file_id, tree_id, self.session_id, info_type, file_info_class))
		return SMB2QueryInfo.response().read(self._recv())

	def _query_filesystem_attributes(self, file_id, tree_id):
		return FileFsAttributeInformation(self._query_info(file_id, tree_id, SMB2_0_INFO_FILESYSTEM, 5).buffer)

	def _fsctl_srv_request_resume_key(self, file_id, tree_id):
		self._send(SMB2IOCTL.request().new(FSCTL_SRV_REQUEST_RESUME_KEY, file_id, tree_id, self.session_id))
		p = SMB2IOCTL.response().read(self._recv())
		return SrvRequestResumeKey(p.buffer).get_resume_key()

	def _fsctl_copychunk(self, file_id, tree_id, buffer):
		self._send(SMB2IOCTL.request().new(FSCTL_SRV_COPYCHUNK_WRITE, file_id, tree_id, self.session_id, buffer))
		SMB2IOCTL.response().read(self._recv())

	def filesystem_is_case_sensitive(self, share):
		""" Returns True if the file system is case sensitive """
		tree_id = self._get_tree_id(share)
		file_id = self._create('', tree_id, SMB2_CREATE_LIST, SMB2_OPLOCK_LEVEL_NONE).file_id
		is_case_sensitive = self._query_filesystem_attributes(file_id, tree_id).iscasesensitive()
		self._close(file_id, tree_id)
		return is_case_sensitive
	
	def listdir(self, share, path, search_pattern='*'):
		""" Lists the contents of a directory """
		if not(type(search_pattern).__name__ == 'list'): search_pattern = [search_pattern]
		file_index = 0
		tree_id = self._get_tree_id(share)

		file_directory_information = []
		for pattern in search_pattern:
			#file_id = self._create(path, tree_id, SMB2_CREATE_LIST, SMB2_OPLOCK_LEVEL_NONE).file_id
			file_id = self._create(path, tree_id, SMB2_CREATE_LIST, SMB2_OPLOCK_LEVEL_II).file_id
			while(True):
				try:
					result = self._query_directory(file_id, tree_id, pattern, file_index)
					file_directory_information += result.file_directory_information
					file_index += result.output_buffer_length
				except ErrorPacketException, e:
					if (e.error_code == 0x80000006): break # NT Status: STATUS_NO_MORE_FILES
					else: raise e
			
			self._close(file_id, tree_id)
		return file_directory_information

	def rename(self, share, old_path, new_path):
		""" Rename a file or directory """
		tree_id = self._get_tree_id(share)
		file_id = self._create(old_path, tree_id, SMB2_CREATE_RENAME, SMB2_OPLOCK_LEVEL_EXCLUSIVE).file_id
		self._set_filename(new_path, file_id, tree_id)
		self._close(file_id, tree_id)

	def mkdir(self, share, new_path):
		""" Make a new directory """
		head, tail = self.split(new_path)
		if ((head) and not(self.exists(share, head))): self.mkdir(share, head)
		tree_id = self._get_tree_id(share)
		file_id = self._create(self.join(head, tail), tree_id, SMB2_CREATE_MKDIR, SMB2_OPLOCK_LEVEL_NONE).file_id
		self._close(file_id, tree_id)

	def copy_server_side(self, src_share, src_path, dst_share, dst_path):
		""" Server side copy. (Recommended to use copy instead) """
		src_tree_id = self._get_tree_id(src_share)
		src_file = self._create(src_path, src_tree_id, SMB2_CREATE_COPYREAD, SMB2_OPLOCK_LEVEL_BATCH)
		src_file_id = src_file.file_id
		src_file_length = src_file.eof
		dst_tree_id = self._get_tree_id(dst_share)
		dst_file_id = self._create(dst_path, dst_tree_id, SMB2_CREATE_COPYWRITE, SMB2_OPLOCK_LEVEL_NONE).file_id
		source_key = self._fsctl_srv_request_resume_key(src_file_id, src_tree_id)
		scc = SrvCopychunkCopy(source_key, src_file_length, self.max_transaction_size)
		while (scc.is_more_chunks()):
			p = scc.get_packet()
			self._fsctl_copychunk(dst_file_id, dst_tree_id, p)
		self._close(src_file_id, src_tree_id)
		self._close(dst_file_id, dst_tree_id)

	def copy_server_to_client(self, src_share, src_path, dst_path, callback=None):
		""" Server to Client copy. (Recommended to use copy instead) """
		src_tree_id = self._get_tree_id(src_share)
		src_file = self._create(src_path, src_tree_id, SMB2_CREATE_COPYREAD, SMB2_OPLOCK_LEVEL_II)
		src_file_id = src_file.file_id
		src_file_length = src_file.eof
		dst_file = open(dst_path, 'wb')
		offset = 0
		while (offset < src_file_length):
			p = self._read(offset, src_file_length, src_file_id, src_tree_id)
			dst_file.write(p.buffer)
			offset += p.data_length
			if (callback != None): callback(src_file_length, offset, src_path, dst_path)
		dst_file.close()
		self._close(src_file_id, src_tree_id)

	def copy_client_to_server(self, src_path, dst_share, dst_path, callback=None):
		""" Client to Server copy. (Recommended to use copy instead) """
		src_file = open(src_path, 'rb')
		src_file_length = os.path.getsize(src_path)
		dst_tree_id = self._get_tree_id(dst_share)
		dst_file_id = self._create(dst_path, dst_tree_id, SMB2_CREATE_COPYWRITE, SMB2_OPLOCK_LEVEL_NONE).file_id
		offset = 0
		while (offset < src_file_length):
			buffer = src_file.read(self.max_write_size)
			p = self._write(offset, src_file_length, dst_file_id, dst_tree_id, buffer)
			offset += p.count
			if (callback != None): callback(src_file_length, offset, src_path, dst_path)
		src_file.close()
		self._close(dst_file_id, dst_tree_id)

	def _copy(self, src_share, src_path, dst_share, dst_path, callback=None):
		if ((src_share == None) and (dst_share == None)):
			shutil.copy(src_path, dst_path)
		elif ((src_share == None) and (dst_share != None)):
			self.copy_client_to_server(src_path, dst_share, dst_path, callback)
		elif ((src_share != None) and (dst_share == None)):
			self.copy_server_to_client(src_share, src_path, dst_path, callback)
		else:
			self.copy_server_side(src_share, src_path, dst_share, dst_path)

	def copy(self, src_share, src_path, dst_share, dst_path, option='', callback=None):
		""" Copies a file or directory from one share/path to another share/path. Use option='r' for recursive copy. """
		if ((option == 'r') or (option == 'R')): # Copy recursive
			try:
				if (src_share != None): items = self.walk(src_share, src_path, True)
				else: items = os.walk(src_path, True)
				for item in items:
					pwd = item[0]
					new_path = self.join(dst_path, pwd.lstrip(src_path))
					if (dst_share != None): self.mkdir(dst_share, new_path)
					else: os.makedirs(new_path)
					for file in item[2]:
						self._copy(src_share, self.join(pwd, file), dst_share, self.join(new_path, file), callback)
			except ErrorPacketException, e:
				if (e.error_code == 0xC0000103): # STATUS_NOT_A_DIRECTORY, can't recursively copy a file.
					self._copy(src_share, src_path, dst_share, dst_path, callback)
				else: raise
		else: # Copy single
			self._copy(src_share, src_path, dst_share, dst_path, callback)

	def move(self, src_share, src_path, dst_share, dst_path):
		""" Moves a file or directory from one share/path to another share/path. """
		if (src_share == dst_share):
			self.rename(src_share, src_path, dst_path)
		else:
			self.copy(src_share, src_path, dst_share, dst_path, 'r')
			self.delete(src_share, src_path, 'r')
		

	def delete(self, share, path, option=''):
		"""
		Deletes a file or directory from share/path.
		
		To recursively delete (ie, for directores that are not empty,
		use option='r' for recursive delete.
		"""
		tree_id = self._get_tree_id(share)
		if ((option == 'r') or (option == 'R')): # Delete recursive
			try:
				for item in self.walk(share, path, False):
					pwd = item[0]
					for file in item[2]:
						file_id = self._create(self.join(pwd,file), tree_id, SMB2_CREATE_DELETE, SMB2_OPLOCK_LEVEL_EXCLUSIVE).file_id
						self._close(file_id, tree_id)
					file_id = self._create(pwd, tree_id, SMB2_CREATE_DELETE, SMB2_OPLOCK_LEVEL_EXCLUSIVE).file_id
					self._close(file_id, tree_id)
			except ErrorPacketException, e:
				if (e.error_code == 0xC0000103): # STATUS_NOT_A_DIRECTORY, can't recursively delete a file.
					file_id = self._create(path, tree_id, SMB2_CREATE_DELETE, SMB2_OPLOCK_LEVEL_EXCLUSIVE).file_id
					self._close(file_id, tree_id)
				else: raise
		else: # Delete single 
			p = self._create(path, tree_id, SMB2_CREATE_DELETE, SMB2_OPLOCK_LEVEL_EXCLUSIVE)
			file_id = p.file_id
			if ((p.isdirectory()) and (len(self.listdir(share, path)) > 2)):
				raise SessionErrorException('Directory '+path+' is not empty.')
			self._close(file_id, tree_id)

	def walk(self, share, top, topdown=True):
		"""Directory tree generator.
	
		For each directory in the directory tree rooted at top (including top
		itself, but excluding '.' and '..'), yields a 3-tuple
	
			dirpath, dirnames, filenames
	
		dirpath is a string, the path to the directory.  dirnames is a list
		of the names of the subdirectories in dirpath (excluding '.' and
		'..'). filenames is a list of the names of the non-directory files
		in dirpath. Note that the names in the lists are just names, with no
		path components. To get a full path (which begins with top) to a file
		or directory in dirpath, do os.path.join(dirpath, name).
	
		If optional arg 'topdown' is true or not specified, the triple for a
		directory is generated before the triples for any of its
		subdirectories (directories are generated top down).  If topdown is
		false, the triple for a directory is generated after the triples for
		all of its subdirectories (directories are generated bottom up).
	
		When topdown is true, the caller can modify the dirnames list in-
		place (e.g., via del or slice assignment), and walk will only recurse
		into the subdirectories whose names remain in dirnames; this can be
		used to prune the search, or to impose a specific order of visiting.
		Modifying dirnames when topdown is false is ineffective, since the
		directories in dirnames have already been generated by the time
		dirnames itself is generated.
	
		This method is a modified version of os.walk and will (hopefully) work
		in exactly the same manner except that it will always report errors.
		"""
		
		items = self.listdir(share, top)
		dirs, files = [], []
		for item in items:
			if (item.isdirectory()):
				if not((item.getname() == '.') or (item.getname() == '..')):
					dirs.append(item.getname())
			else:
				files.append(item.getname())
		
		if topdown: yield top, dirs, files
		for name in dirs:
			path = (self.join(top,name), name)[top == '']
			for x in self.walk(share, path, topdown): yield x
		if not topdown: yield top, dirs, files

	@staticmethod
	def join(*p):
		"""Join two or more pathname components, inserting '\' as needed"""
		path = ''
		for b in p:
			b = b.strip('\\')
			if (b != ''):
				if (path != ''): path += '\\' + b
				else: path = b
		return path

	def split(self, path):
		"""Split a pathname.  Returns tuple "(head, tail)" where "tail" is
		everything after the final slash.  Either part may be empty."""
		path = path.strip('\\')
		i = path.rfind('\\') + 1
		head, tail = path[:i], path[i:]
		if (head != ''): head = head.rstrip('\\')
		return head, tail
	
	def exists(self, share, path):
		"""Test whether a path exists"""
		tree_id = self._get_tree_id(share)
		try:
			p = self._create(path, tree_id, SMB2_CREATE_EXIST, SMB2_OPLOCK_LEVEL_NONE)
			file_id = p.file_id
		except ErrorPacketException, e:
			return False
		
		self._close(file_id, tree_id)
		return True
	
	def is_file_unlocked(self, share, path):
		""" Returns True if the file is unlocked. """
		tree_id = self._get_tree_id(share)
		p = self._create(path, tree_id, SMB2_CREATE_LOCK, SMB2_OPLOCK_LEVEL_EXCLUSIVE)
		file_id = p.file_id
		# The only time I have seen the SMB2_FLAGS_ASYNC_COMMAND flag set is
		# immediately after a file is opened or closed and it is accompanied
		# by a STATUS_PENDING packet. It seems to open a handle to a file
		# without a name (although this could be a bug in Wireshark). Since
		# I'm not sure what is going on exactly, and there is no documentation
		# about this in [MS-SMB2] I am returning False to be safe.
		#
		# Maybe this is caused by a lock on the file? If so, this could
		# potentially cause the system to hang indefinitely waiting for the 
		# file to be unlocked.
		if (p.header.flags & SMB2_FLAGS_ASYNC_COMMAND != 0): result = False
		else: result = (p.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
		
		self._close(file_id, tree_id)
		return result

	def is_directory_unlocked(self, share, path):
		""" Returns True if the directory is unlocked. """
		tree_id = self._get_tree_id(share)
		p = self._create(path, tree_id, SMB2_CREATE_LIST, SMB2_OPLOCK_LEVEL_EXCLUSIVE)
		file_id = p.file_id
		self._close(file_id, tree_id)
		return p.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE

###################################################################################################		
###               End of SMB2 class, custom exceptions and packet classes follow                ###
###################################################################################################

#TODO: Create a better custom exception scheme, then add some error checking.
class InvalidPacketException(Exception):
	def __init__(self, value):
		self.parameter = value
	def __str__(self):
		return repr(self.parameter)

class ErrorPacketException(Exception):
	def __init__(self, error_code):
		self.error_string = Util.lookup_status_string(error_code)
		self.error_code = error_code
	def __str__(self):
		return "'(NTSTATUS: 0x"+("%.8x".upper() % self.error_code)+") "+self.error_string+"'"

class UnexpectedPacketException(Exception):
	def __init__(self, cmd_code):
		self.cmd_code = cmd_code
	def __str__(self):
		return "error"

class SessionErrorException(Exception): pass
class UnsupportedFeatureException(Exception): pass
class SMB2TimeoutException(Exception): pass


class SMB2Packet(object):
	class request(object):
		def counter():
			i = 0
			while (True):
				yield i
				i += 1
				if (i > 2**64): i = 0
		get_message_id = counter().next

		def read(self):
			raise UnsupportedFeatureException('Cannot read request packet, this is only a client.')

	class response(object):
		def new(self):
			raise UnsupportedFeatureException('Cannot create new response packet, this is only a client.')


class SMB2Header(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, command, flags, tree_or_async_id, session_id, credit_request):
			self.protocol_id = 0x424D53FE
			self.header_size = 0x40 
			self.credit_charge = 0x0 # zero for 2.002, need to implement for 2.1
			self.status = 0x0
			self.command = command
			self.credit_request = credit_request
			self.flags = flags
			self.next_command = 0x0
			self.message_id = self.get_message_id()
			self.session_id = session_id
			self.signature = '\x00'*16
			if (flags & SMB2_FLAGS_ASYNC_COMMAND):
				self.async_id = tree_or_async_id
				header = pack('<IHHIHHIIQQQ16s', self.protocol_id, self.header_size, self.credit_charge, self.status, self.command, self.credit_request, self.flags, self.next_command, self.message_id, self.async_id, self.session_id, self.signature)
			else:
				self.process_id = 0xFEFF
				self.tree_id = tree_or_async_id
				header = pack('<IHHIHHIIQIIQ16s', self.protocol_id, self.header_size, self.credit_charge, self.status, self.command, self.credit_request, self.flags, self.next_command, self.message_id, self.process_id, self.tree_id, self.session_id, self.signature)
			return header
	
	class response(SMB2Packet.response):	
		def read(self, data):
			self.protocol_id, self.header_size, self.credit_charge, self.status, self.command, self.credit_request, self.flags = unpack('<IHHIHHI', data[0:20])
			if (self.flags & SMB2_FLAGS_ASYNC_COMMAND):
				self.next_command, self.message_id, self.async_id, self.session_id, self.signature = unpack('<IQQQ16s', data[20:64])
			else:
				self.next_command, self.message_id, self.process_id, self.tree_id, self.session_id, self.signature = unpack('<IQIIQ16s', data[20:64])
			# Don't raise exception when 0x00000000 because that means it was successful, or 0xc0000016 which means 'more processing required' because I already know that and don't want to throw an exception
			if ((self.status != 0x00000000) and (self.status != 0xc0000016)): raise ErrorPacketException(self.status)
			return self


class SMB2Negotiate(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self):
			""" Creates a SMB2 NEGOTIATE Request """
			self.header = SMB2Header.request().new(SMB2_CMD_NEGOTIATE, 0x0, 0x0, 0x0, 0x7E)
			self.message_size = 0x24
			self.dialect_count = 0x1 # for now we only support 2.002 
			self.security_mode = 0x1
			self.reserved = 0x0
			self.capabilities = 0x0
			self.client_guid = pack('QQ',0,0) # zero for 2.002, need to implement for 2.1
			self.client_start_time = 0x0
			self.dialects = 0x202 # for now we only support 2.002
			message = pack('<HHHHI16sQH', self.message_size, self.dialect_count, self.security_mode, self.reserved, self.capabilities, self.client_guid, self.client_start_time, self.dialects)
			return self.header + message
	
	class response(SMB2Packet.response):	
		def read(self, data):
			""" Reads a SMB2 NEGOTIATE Response """
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.security_mode, self.dialect_revision, self.reserved, self.server_guid, self.capabilities, self.max_transaction_size, self.max_read_size, self.max_write_size, self.system_time, self.server_start_time, self.security_buffer_offset, self.security_buffer_size, self.reserved2 = unpack('<HHHH16sIIIIQQHHI', data[68:132])
			return self


class SMB2SessionSetup(SMB2Packet):
	# NOTE: This class is setup differently than the rest. This is because I needed to preserve some variables 
	#       and this was the easiest way to do it. Unfortunately it came at the expense of causing the interface to 
	#       be inconsistent but I figure, who is really going to care? It's not like a lot of people are going to be
	#       using these classes directly.  
	def __init__(self, user='', password='', user_domain='', server_machine_name=''):
		self.user = user
		self.password = password
		self.user_domain = user_domain
		self.server_machine_name = server_machine_name

	def new_request(self, challenge=None, target_info=None, session_id=0x0):
		"""
		If challenge == None, a negotiation token is created 
		If challenge != None, an authentication token is created.
		"""
		self.header = SMB2Header.request().new(SMB2_CMD_SESSION_SETUP, 0x0, 0x0, session_id, 0x1)
		self.message_size = 0x19
		self.vc_number = 0x0
		self.security_mode = 0x1
		self.capabilities = 0x0
		self.channel = 0x0
		self.previous_session_id = 0

		if (challenge == None):
			self.negotiate_message = NTLM.Negotiate()
			self.security_buffer = GSS.neg_token_init(self.negotiate_message.encode())
		else:
			self.authenticate_message = NTLM.Authenticate(challenge, target_info, self.negotiate_message.encode() + self.challenge_message.encode(), self.user, self.password, self.user_domain, self.server_machine_name)
			#self.security_buffer = GSS.neg_token_resp(self.authenticate_message.encode(), self.authenticate_message.mech_list_mic[0:8])
			self.security_buffer = GSS.neg_token_resp(self.authenticate_message.encode())

		self.security_buffer_offset = 0x58
		self.security_buffer_size = len(self.security_buffer)
		message = pack('<HBBIIHHQ'+str(self.security_buffer_size)+'s', self.message_size, self.vc_number, self.security_mode, self.capabilities, self.channel, self.security_buffer_offset, self.security_buffer_size, self.previous_session_id, self.security_buffer)
		return self.header + message

	def read_response(self, data):
		""" Reads a SMB2 SESSION_SETUP Response and NTLMSSP CHALLENGE_MESSAGE """
		self.packet_size = unpack('<I',data[0:4])[0]
		self.header = SMB2Header.response().read(data[4:68])
		self.message_size, self.session_flags, self.security_buffer_offset, self.security_buffer_size = unpack('<HHHH', data[68:76])

		if not(self.security_buffer_size > 0): raise InvalidPacketException('Missing security buffer')
		self.gss_token = data[self.security_buffer_offset+4:self.security_buffer_offset+self.security_buffer_size+4]
		
		neg_state, ntlm_token = GSS.unwrap_neg_token_resp(self.gss_token)
		if (neg_state == GSS.STATUS[1]): 
			self.challenge_message = NTLM.Challenge(ntlm_token)
		

class SMB2TreeConnect(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, session_id, path):
			path = path.encode('utf-16-le')
			self.header = SMB2Header.request().new(SMB2_CMD_TREE_CONNECT, 0x0, 0x0, session_id, 0x1)
			self.message_size = 9
			self.reserved = 0
			self.path_offset = 72
			self.path_length = len(path)
			self.path = path
			message = pack('<HHHH', self.message_size, self.reserved, self.path_offset, self.path_length) + self.path
			return self.header + message

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.share_type, self.reserved, self.share_flags, self.capabilities, self.maximal_access = unpack('<HBBIII', data[68:84])
			return self


class SMB2Create(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, tree_id, session_id, name, create_action, oplock_level=SMB2_OPLOCK_LEVEL_EXCLUSIVE):
			if (create_action == SMB2_CREATE_LIST):
				self.desired_access = FILE_LIST_DIRECTORY
				self.create_options = FILE_DIRECTORY_FILE
				self.create_disposition = FILE_OPEN
			elif (create_action == SMB2_CREATE_RENAME):
				self.desired_access = DELETE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
				self.create_options = 0
				self.create_disposition = FILE_OPEN
			elif (create_action == SMB2_CREATE_COPYREAD):
				self.desired_access = FILE_READ_DATA | FILE_WRITE_DATA
				self.create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE | FILE_NO_EA_KNOWLEDGE
				self.create_disposition = FILE_OPEN
			elif (create_action == SMB2_CREATE_COPYWRITE):
				self.desired_access = FILE_READ_DATA | FILE_WRITE_DATA
				self.create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE | FILE_NO_EA_KNOWLEDGE
				self.create_disposition = FILE_CREATE
			elif (create_action == SMB2_CREATE_MKDIR):
				self.desired_access = FILE_ADD_SUBDIRECTORY
				self.create_options = FILE_DIRECTORY_FILE
				self.create_disposition = FILE_CREATE
			elif (create_action == SMB2_CREATE_EXIST):
				self.desired_access = FILE_READ_DATA | FILE_WRITE_DATA
				self.create_options = 0
				self.create_disposition = FILE_OPEN
			elif (create_action == SMB2_CREATE_DELETE):
				self.desired_access = DELETE | FILE_DELETE_CHILD
				self.create_options = FILE_DELETE_ON_CLOSE
				self.create_disposition = FILE_OPEN
			elif (create_action == SMB2_CREATE_LOCK):
				self.desired_access = FILE_SHARE_READ
				self.create_options = 0
				self.create_disposition = FILE_OPEN

			self.header = SMB2Header.request().new(SMB2_CMD_CREATE, 0x0, tree_id, session_id, 0x1)
			self.message_size = 57
			self.security_flags = 0
			self.oplock_level = oplock_level
			self.impersonation_level = 0x02 # [MS-SMB2] says server must ignore this field, using 0x02 because that's what Windows7 uses. 
			self.smb_create_flags = 0
			self.reserved = 0
			#TODO: Figure out file attributes. This probably should NOT be 0 for every case but it seems to work so far. 
			self.file_attributes = 0  #??
			#TODO: Requesting Read/Write/Delete access is overkill in most situations and could lead to an unnecessary failure.
			self.share_access = 0x07 #??
			self.name = name.encode('utf-16-le')				
			self.name_offset = 120
			self.name_length = len(self.name)
			padding_length = 8 - (self.name_length % 8)
			self.name += " "*padding_length
			extra = pack('<IHHHHI4sIQQ',0x0, 0x10, 0x04, 0x0, 0x18, 0x10, 'DHnQ', 0x0, 0x0, 0x0)
			self.context_offset = 120 + len(self.name)#self.name_length
			self.context_length = len(extra)
			message = pack('<HBBIQQIIIIIHHII', self.message_size, self.security_flags, self.oplock_level, self.impersonation_level, self.smb_create_flags, self.reserved, self.desired_access, self.file_attributes, self.share_access, self.create_disposition, self.create_options, self.name_offset, self.name_length, self.context_offset, self.context_length)
			return self.header + message + self.name + extra

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.oplock_level, self.reserved, self.create_action, self.creation_time, self.last_access_time, self.last_write_time, self.change_time, self.allocation_size, self.eof, self.file_attributes, self.reserved2, self.file_id, self.context_offset, self.context_length = unpack('<HBBIQQQQQQII16sII', data[68:156])
			self.buffer = data[156:]
			return self
	
		def isdirectory(self):
			return (self.file_attributes & FILE_ATTRIBUTE_DIRECTORY) != 0
			
		def isreadonly(self):
			return (self.file_attributes & FILE_ATTRIBUTE_READONLY) != 0
			
		def ishidden(self):
			return (self.file_attributes & FILE_ATTRIBUTE_HIDDEN) != 0


class FileDirectoryInformation(object):
	def __init__(self, data):
		self.file_index, self.creation_time, self.last_access_time, self.last_write_time, self.last_change_time, self.eof, self.allocation_size, self.file_attributes, self.file_name_length = unpack('<IQQQQQQII', data[0:60])
		self.file_name = unicode(data[60 : 60 + self.file_name_length], "utf-16-le")

	def get_name(self):
		return self.file_name

	def is_file(self):
		return not(self.file_attributes & FILE_ATTRIBUTE_DIRECTORY) != 0

	def is_directory(self):
		return (self.file_attributes & FILE_ATTRIBUTE_DIRECTORY) != 0
	
	def is_hidden(self):
		return (self.file_attributes & FILE_ATTRIBUTE_HIDDEN) != 0
	
	def is_readonly(self):
		return (self.file_attributes & FILE_ATTRIBUTE_READONLY) != 0
		
	def get_atime(self):
		return self.last_access_time
	
	def get_mtime(self):
		return self.last_change_time
	
	def get_ctime(self):
		return self.creation_time
	
	def get_size(self):
		return self.eof
	
	
class FileFsAttributeInformation(object):
	def __init__(self, data):
		self.file_system_attributes, self.max_component_name_length, self.file_system_name_length = unpack('<III', data[0:12])
		self.file_system_name = data[12:12+self.file_system_name_length]
		
	def iscasesensitive(self):
		#return (self.file_system_attributes & FILE_CASE_SENSITIVE_SEARCH) != 0
		return (self.file_system_attributes & FILE_CASE_PRESERVED_NAMES) != 0

		
class SMB2QueryDirectory(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, file_id, tree_id, session_id, search_pattern='*', file_index=0x0, flags=0x0):
			if ((search_pattern == '') or (search_pattern == None)): search_pattern = '*'
			search_pattern = search_pattern.encode('utf-16-le')
			self.header = SMB2Header.request().new(SMB2_CMD_QUERY_DIRECTORY, 0x0, tree_id, session_id, 0x1)
			self.message_size = 33
			self.file_info_class = 0x01 # Basic info ??
			self.flags = flags
			self.file_index = file_index
			if ((self.file_index == 0) and (self.flags == SMB2_INDEX_SPECIFIED)): raise Exception('file_index must be suppliued if the flags=SMB2_INDEX_SPECIFIED is set')
			self.file_id = file_id
			self.filename_offset = (self.message_size+64-1, 0)[search_pattern == '']
			self.filename_length = len(search_pattern)
			self.output_buffer_length = 65536-(64+33)
			message = pack('<HBBI16sHHI', self.message_size, self.file_info_class, self.flags, self.file_index, self.file_id, self.filename_offset, self.filename_length, self.output_buffer_length)
			return self.header + message + search_pattern
		
	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.output_buffer_offset, self.output_buffer_length = unpack('<HHI', data[68:76])
			self.buffer = data[76:]
			self.file_directory_information = []
			next_entry_offset = None
			while ((len(self.buffer) > 4) and not(next_entry_offset == 0)):
				next_entry_offset = unpack('<I', self.buffer[0:4])[0]
				if not (next_entry_offset == 0): info_data = self.buffer[4:next_entry_offset]
				else: info_data = self.buffer[4:]
				self.file_directory_information.append(FileDirectoryInformation(info_data))
				self.buffer = self.buffer[next_entry_offset:]
			return self


class FileRenameInformation(object):
	def new(self, filename):
		filename = filename.encode('utf-16-le')
		self.replace_if_exists = False
		self.reserved = '\x00\x00\x00\x00\x00\x00\x00'
		self.root_directory = 0x00
		self.filename_length = len(filename)
		self.filename = filename
		self.pad = "\x00"*6
		file_information = pack('<B7sQI', self.replace_if_exists, self.reserved, self.root_directory, self.filename_length) + self.filename + self.pad
		return file_information
	

class SMB2SetInformation(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, file_information_class, file_information, file_id, tree_id, session_id):
			self.header = SMB2Header.request().new(SMB2_CMD_SET_INFO, 0x0, tree_id, session_id, 0x1)
			self.message_size = 33
			self.info_type = 0x01 # ??
			self.file_info_class = file_information_class
			self.buffer_length = len(file_information)
			self.buffer_offset = 96
			self.reserved = 0
			self.additional_information = 0
			self.file_id = file_id
			message = pack('<HBBIHHI16s', self.message_size, self.info_type, self.file_info_class, self.buffer_length, self.buffer_offset, self.reserved, self.additional_information, self.file_id)
			return self.header + message + file_information

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size = unpack('<H', data[68:70])[0]


class SMB2Close(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, file_id, tree_id, session_id):
			self.header = SMB2Header.request().new(SMB2_CMD_CLOSE, 0x0, tree_id, session_id, 0x1)
			self.message_size = 0x18
			self.flags = 0x00
			self.reserved = 0x00
			self.file_id = file_id
			message = pack('<HHI16s', self.message_size, self.flags, self.reserved, self.file_id)
			return self.header + message

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.flags, self.reserved, self.creation_time, self.last_access_time, self.last_write_time, self.change_time, self.allocation_size, self.eof, self.file_attributes = unpack('<HHIQQQQQQI', data[68:128])
			return self	


class SMB2TreeDisconnect(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, tree_id, session_id):
			self.header = SMB2Header.request().new(SMB2_CMD_TREE_DISCONNECT, 0x0, tree_id, session_id, 0x1)
			self.message_size = 0x04
			self.reserved = 0x00
			message = pack('<HH', self.message_size, self.reserved)
			return self.header + message
	
	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.reserved = unpack('<HH', data[68:72])
			return self	


class SMB2Logoff(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, session_id):
			self.header = SMB2Header.request().new(SMB2_CMD_LOGOFF, 0x0, 0x0, session_id, 0x1)
			self.message_size = 0x04
			self.reserved = 0x00
			message = pack('<HH', self.message_size, self.reserved)
			return self.header + message
	
	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.reserved = unpack('<HH', data[68:72])
			return self	


class SrvCopychunkCopy(object):
	CHUNKSIZE = 1*1024*1024 # 1MB chunks
	def __init__(self, source_key, file_length, max_transaction_size):
		# The following 4 lines create the SRV_COPYCHUNK_COPY packet
		self.source_key = source_key
		self.chunk_total = int(math.ceil(file_length/self.CHUNKSIZE))
		self.reserved = 0x00000000
		self.chunk_offset = 0
		self.file_length = file_length
		self.max_transaction_size = max_transaction_size
	
	def get_packet(self):
		# Max chunk_count experimentally found to be 16, any more returns STATUS_INVALID_PARAMETER. Couldn't find any documentation on this though...?
		# This means self.max_transaction_size would have to be < 480 bytes to ever be a min... possibly take this out? 
		self.chunk_count = min(math.floor((self.max_transaction_size-96)/24), self.chunk_total-self.chunk_offset, 16)
		self.packet = pack('<24sII', self.source_key, self.chunk_count, self.reserved)
		# The following creates an array of SRV_COPYCHUNK packets
		self.offset = self.chunk_offset * self.CHUNKSIZE
		while (self.file_length-self.offset > 0):
			self.length = min(self.CHUNKSIZE, self.file_length-self.offset)
			self.reserved2 = 0x00000000
			self.packet += pack('<QQII', self.offset, self.offset, self.length, self.reserved2)
			self.offset += self.length
		self.chunk_offset += self.chunk_count
		return self.packet
	
	def is_more_chunks(self):
		return (self.chunk_offset < self.chunk_total)


class SrvRequestResumeKey(object):
	def __init__(self, data):
		self.resume_key, self.context_length = unpack('<24sI', data[0:28])
		self.context = data[28:] # unused
	
	def get_resume_key(self):
		return self.resume_key

	
class SMB2IOCTL(SMB2Packet):
	""" The SMB2 IOCTL Request packet is sent by a client to issue an implementation-specific file system control or device control (FSCTL/IOCTL) command across the network. """
	class request(SMB2Packet.request):
		def new(self, ctl_code, file_id, tree_id, session_id, buffer=''):
			self.header = SMB2Header.request().new(SMB2_CMD_IOCTL, 0x0, tree_id, session_id, 0x1)
			self.buffer = buffer
			self.message_size = 57
			self.reserved = 0x00
			self.ctl_code = ctl_code
			self.file_id = file_id
			self.input_offset = (0, 120)[len(self.buffer) > 0]
			self.input_count = len(self.buffer)
			self.max_input_response = 0 #(2**32)-1 # ??
			self.output_offset = 0x00
			self.output_count = 0x00
			if (ctl_code == FSCTL_SRV_REQUEST_RESUME_KEY): self.max_output_response = 32
			elif (ctl_code == FSCTL_SRV_COPYCHUNK_WRITE) or (ctl_code == FSCTL_SRV_COPYCHUNK): self.max_output_response = 12
			#self.max_output_response = 1024 #(2**32)-1 # ??
			self.flags = 0x01 # the request is FSCTL (0 = IOCTL)
			self.reserved2 = 0x00
			message = pack('<HHI16sIIIIIIII', self.message_size, self.reserved, self.ctl_code, self.file_id, self.input_offset, self.input_count, self.max_input_response, self.output_offset, self.output_count, self.max_output_response, self.flags, self.reserved2)
			return self.header + message + self.buffer

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.reserved, self.ctl_code, self.file_id, self.input_offset, self.input_count, self.output_offset, self.output_count, self.flags, self.reserved2 = unpack('<HHI16sIIIIII', data[68:116])
			self.buffer = data[116:]
			return self	


class SMB2Read(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, offset, file_length, file_id, tree_id, session_id, max_read_size):
			self.header = SMB2Header.request().new(SMB2_CMD_READ, 0x0, tree_id, session_id, 0x1)
			self.message_size = 49
			self.padding = 0x00
			self.reserved = 0x00
			self.length = min(max_read_size, file_length-offset)
			self.offset = offset
			self.file_id = file_id
			self.minimum_count = 0x00000000 #??
			self.channel = 0x00000000
			self.remaining_bytes = file_length-offset
			self.read_channel_info_offset = 0x0000
			self.read_channel_info_length = 0x0000
			self.buffer = '\x00'
			message = pack('<HBBIQ16sIIIHH', self.message_size, self.padding, self.reserved, self.length, self.offset, self.file_id, self.minimum_count, self.channel, self.remaining_bytes, self.read_channel_info_offset, self.read_channel_info_length)
			return self.header + message + self.buffer

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.data_offset, self.reserved, self.data_length, self.data_remaining, self.reserved2 = unpack('<HBBIII', data[68:84])
			self.buffer = data[84:]
			return self	


class SMB2Write(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, offset, file_length, file_id, tree_id, session_id, buffer):
			self.header = SMB2Header.request().new(SMB2_CMD_WRITE, 0x0, tree_id, session_id, 0x1)
			self.message_size = 49
			self.data_offset = 112
			self.length = len(buffer)
			self.offset = offset
			self.file_id = file_id
			self.channel = 0x00000000
			self.remaining_bytes = file_length-offset
			self.write_channel_info_offset = 0x0000
			self.write_channel_info_length = 0x0000
			self.flags = 0x00000000 # only supported in SMB 2.1
			self.buffer = buffer
			message = pack('<HHIQ16sIIHHI', self.message_size, self.data_offset, self.length, self.offset, self.file_id, self.channel, self.remaining_bytes, self.write_channel_info_offset, self.write_channel_info_length, self.flags)
			return self.header + message + self.buffer

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.reserved, self.count, self.remaining, self.write_channel_info_offset, self.write_channel_info_length = unpack('<HHIIHH', data[68:84])
			self.buffer = data[84:]
			return self	


class SMB2QueryInfo(SMB2Packet): # fully customizable
	class request(SMB2Packet.request):
		def new(self, file_id, tree_id, session_id, info_type, file_info_class, additional_info=0, flags=0,  buffer=''):
			self.header = SMB2Header.request().new(SMB2_CMD_QUERY_INFO, 0x0, tree_id, session_id, 0x1)
			self.message_size = 41
			self.info_type = info_type
			self.file_info_class = file_info_class
			self.output_buffer_length = 2**16
			self.input_buffer_offset = (104, 0)[buffer == '']
			self.reserved = 0
			self.input_buffer_length = len(buffer)
			self.additional_information = additional_info
			self.flags = flags
			self.file_id = file_id
			self.buffer = buffer
			message = pack('<HBBIHHIII16s', self.message_size, self.info_type, self.file_info_class, self.output_buffer_length, self.input_buffer_offset, self.reserved, self.input_buffer_length, self.additional_information, self.flags, self.file_id)
			return self.header + message + self.buffer

	class response(SMB2Packet.response):	
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.output_buffer_offset, self.output_buffer_length = unpack('<HHI', data[68:76])
			self.buffer = data[4+self.output_buffer_offset:4+self.output_buffer_offset+self.output_buffer_length]
			return self	


class SMB2Lock(SMB2Packet):
	class request(SMB2Packet.request):
		def new(self, file_id, tree_id, session_id, file_length, flags):
			self.header = SMB2Header.request().new(SMB2_CMD_LOCK, 0x0, tree_id, session_id, 0x1)
			self.message_size = 48
			self.lock_count = 1
			self.lock_sequence = 0
			self.file_id = file_id
			self.offset = 0 # always lock the entire file
			self.length = file_length # always lock the entire file
			self.flags = flags
			self.reserved = 0
			message = pack('<HHI16s', self.message_size, self.lock_count, self.lock_sequence, self.file_id)
			lock_element = pack('<QQII', self.offset, self.length, self.flags, self.reserved)
			return self.header + message + lock_element
		
	class response(SMB2Packet.response):
		def read(self, data):
			self.packet_size = unpack('<I',data[0:4])[0]
			self.header = SMB2Header.response().read(data[4:68])
			self.message_size, self.reserved = unpack('<HH', data[68:72])
			return self	


class Util(object):
	@staticmethod
	def ntstatus_db_select(nt_value):
		""" Returns a list of value-code pairs corresponding the the specified ntstatus value """
		_conn = sqlite3.connect(os.path.join(os.getcwd(),"ntstatus.db"))
		_c = _conn.cursor()
		_c.execute("SELECT * FROM ntstatus WHERE value=?", [nt_value])
		rows = []
		row = _c.fetchone()
		while not(row == None):
			rows.append(row)
			row = _c.fetchone()
		_c.close()
		return rows
	
	@staticmethod
	def lookup_status_string(nt_value):
		""" Returns the code for the first value found with nt_value """
		status = Util.ntstatus_db_select(nt_value)
		if (len(status) == 0): return "UNKNOWN_NT_STATUS" 
		return status[0][1]	
	
	@staticmethod
	def toHex(s):
		lst = []
		for ch in s:
			hv = hex(ord(ch)).replace('0x', '')
			if len(hv) == 1:
				hv = '0'+hv
			lst.append(hv)
		return reduce(lambda x,y:x+y, lst)
	
	@staticmethod
	def print_hex(s):
		hex_s = Util.toHex(s)
		for i in xrange(0, len(hex_s)/32+1):
			for j in xrange(0,8):
				print hex_s[0+i*32+j*2 : 2+i*32+j*2],
			print "",
			for j in xrange(8,16):
				print hex_s[0+i*32+j*2 : 2+i*32+j*2],
			print
	